/** Module:	variant.d: A stack-based variant implemention

  Written in the D programming language 1.0

Authors:	Wei Li <oldrev@gmail.com>
Copyright:	Copyright (C) 2007 Wei Li.
License:	BSD
*/ 

module dotmars.base.variant;

import dotmars.base.tuple;
import dotmars.base.stdexcept;

private template MaxSizeImp(T, V...)
{
    static if(V.length > 0)
        private const int tailResult = MaxSizeImp!(V).result;
    else 
        private const int tailResult = T.sizeof;

    public const int result = T.sizeof > tailResult ? T.sizeof : tailResult;
};

private template MaxSize(TList...)
{
    const int MaxSize = MaxSizeImp!(TList).result;
}


/**
  A Variant implemention inspired by boost.variant
*/
struct Variant(TList...)
{
	//ensure void was not in the TList
	static assert( IndexOf!(void, TList) < 0, "Error: Invalid type in the TList");

    public alias TList TypeList;
    public alias Variant!(TypeList) SelfType;
	private alias ubyte[MaxSize!(TypeList)] Holder;

    private Holder  m_held;
    private int     m_which = -1;

	template indexof(T)
	{
		const int indexof = IndexOf!(T, TypeList);
	}

    public static SelfType opCall(ValueType)(ValueType val)
    {
        SelfType var;
        var.opAssign!(ValueType)(val);
        return var;
    }

    public int which()
    {
        return m_which;
    }

    public ValueType opAssign(ValueType)(ValueType rhs)
    {
		const auto i = IndexOf!(ValueType, TypeList);
		static assert(i >= 0);
		ValueType* heldPtr = cast(ValueType*)m_held.ptr;
		*heldPtr = rhs;
		m_which = i;

		return rhs;
    }

	public bool opEquals(ValueType)(ValueType rhs)
	{
        assert(!empty);

		static if(is(ValueType == SelfType))
		{
			foreach(T; TypeList)
			{
				const int i = IndexOf!(T, TypeList);
				if(i == which)
				{
					return (rhs.which == which) && (get!(T) == rhs.get!(T));
				}
			}
		}
		else
		{
			const int i = IndexOf!(ValueType, TypeList);
			static assert(i >= 0);
		
			return get!(ValueType)() == rhs;
		}
	}


	public int opCmp(ValueType)(ValueType rhs) //used for hash or something else
	out(result)
	{
		assert(result == 0 || result == 1 || result == -1);
	}
	body
	{
		if(rhs == *this)return 0;

		static if(is(ValueType == SelfType))
		{
			foreach(T; TypeList)
			{
				const int i = IndexOf!(T, TypeList);
				if((i == which) && (rhs.which == which))
				{
					return get!(T) < rhs.get!(T) ? -1 : 1;
				}
			}
		}
		else
		{
			const int i = IndexOf!(ValueType, TypeList);
			static assert(i >= 0);
		
			return get!(ValueType)() < rhs ? -1 : 1;
		}
	}
	
	public TypeInfo type()
	{
		if(which < 0)return typeid(void);
		foreach(T; TypeList)
		{
			const int i = IndexOf!(T, TypeList);
			if(i == which)
			{
				return typeid(TypeList[i]);
			}
		}
	}

    public ValueType get(ValueType)()
    {		
        if(indexof!(ValueType) != which) 
			throw new BadCastException("Invalid type");

        return *(cast(ValueType*)m_held.ptr);
    }

	//An nice name stolen from std.variant
	public ValueType* peek(ValueType)()
	{
		//hey, I say again, we need the ref!
		if(indexof!(ValueType) != which) 
			return null;

		return cast(ValueType*)m_held.ptr;
	}

    public bool empty()
    {
        return m_which < 0;
    }

	public void swap(inout SelfType var)
	{
		Holder h;
		h[] = m_held[];
		int w = which;
		m_held[] = var.m_held[];
		m_which = var.which;
		var.m_held[] = h[];
		var.m_which = w;
	}


	public hash_t toHash()
	{
		return type.getHash(m_held.ptr);
	}

	//TODO: Make it sense.
	public string toString()
	{
		return "variant";
	}

	//overloading for arithmetic operators, copied from Tango :)

	typeof(T+T) opAdd(T)( T rhs )      { return get!(T) + rhs; }
	typeof(T+T) opAdd_r(T)( T lhs )    { return lhs + get!(T); }
	typeof(T-T) opSub(T)( T rhs )      { return get!(T) - rhs; }
	typeof(T-T) opSub_r(T)( T lhs )    { return lhs - get!(T); }
	typeof(T*T) opMul(T)( T rhs )      { return get!(T) * rhs; }
	typeof(T*T) opMul_r(T)( T lhs )    { return lhs * get!(T); }
	typeof(T/T) opDiv(T)( T rhs )      { return get!(T) / rhs; }
	typeof(T/T) opDiv_r(T)( T lhs )    { return lhs / get!(T); }
	typeof(T%T) opMod(T)( T rhs )      { return get!(T) % rhs; }
	typeof(T%T) opMod_r(T)( T lhs )    { return lhs % get!(T); }
	typeof(T&T) opAnd(T)( T rhs )      { return get!(T) & rhs; }
	typeof(T&T) opAnd_r(T)( T lhs )    { return lhs & get!(T); }
	typeof(T|T) opOr(T)( T rhs )       { return get!(T) | rhs; }
	typeof(T|T) opOr_r(T)( T lhs )     { return lhs | get!(T); }
	typeof(T^T) opXor(T)( T rhs )      { return get!(T) ^ rhs; }
	typeof(T^T) opXor_r(T)( T lhs )    { return lhs ^ get!(T); }
	typeof(T<<T) opShl(T)( T rhs )     { return get!(T) << rhs; }
	typeof(T<<T) opShl_r(T)( T lhs )   { return lhs << get!(T); }
	typeof(T>>T) opShr(T)( T rhs )     { return get!(T) >> rhs; }
	typeof(T>>T) opShr_r(T)( T lhs )   { return lhs >> get!(T); }
	typeof(T>>>T) opUShr(T)( T rhs )   { return get!(T) >>> rhs; }
	typeof(T>>>T) opUShr_r(T)( T lhs ) { return lhs >>> get!(T); }
	typeof(T~T) opCat(T)( T rhs )      { return get!(T) ~ rhs; }
	typeof(T~T) opCat_r(T)( T lhs )    { return lhs ~ get!(T); }

	T opAddAssign(T)( T rhs )  { return (*this = get!(T) + rhs); }
	T opSubAssign(T)( T rhs )  { return (*this = get!(T) - rhs); }
	T opMulAssign(T)( T rhs )  { return (*this = get!(T) * rhs); }
	T opDivAssign(T)( T rhs )  { return (*this = get!(T) / rhs); }
	T opModAssign(T)( T rhs )  { return (*this = get!(T) % rhs); }
	T opAndAssign(T)( T rhs )  { return (*this = get!(T) & rhs); }
	T opOrAssign(T)( T rhs )   { return (*this = get!(T) | rhs); }
	T opXorAssign(T)( T rhs )  { return (*this = get!(T) ^ rhs); }
	T opShlAssign(T)( T rhs )  { return (*this = get!(T) << rhs); }
	T opShrAssign(T)( T rhs )  { return (*this = get!(T) >> rhs); }
	T opUShrAssign(T)( T rhs ) { return (*this = get!(T) >>> rhs); }
	T opCatAssign(T)( T rhs )  { return (*this = get!(T) ~ rhs); }

	T opIndex(T, K)(K key) { return get!(T)[key]; }
	T opIndexAssign(T, K)(T rhs, K key) { return get!(T)[key] = rhs; }

}

